package org.game.module.datingbo.handler;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.sojoys.artifact.manager.MessageManager;
import com.sojoys.artifact.tools.ToolError;
import com.xiaoleilu.hutool.date.DateUtil;
import org.cfg.DatingCfg;
import org.cfg.DatingRuleCfg;
import org.cfg.cache.DatingCfgCache;
import org.cfg.cache.DatingRuleCfgCache;
import org.cfg.cache.DiscreteDataCfgCache;
import org.constant.*;
import org.error.GameErrorCode;
import org.game.constant.ElementCollectionConstant;
import org.game.db.bean.*;
import org.game.db.cache.*;
import org.game.log.LogDating;
import org.game.log.builder.LogDsp;
import org.game.log.dsp.DatingDsp;
import org.game.log.dsp.GoodsDsp;
import org.game.manager.DatingManager;
import org.game.manager.ElementCollectManager;
import org.game.manager.ItemManager;
import org.game.manager.ItemPackageHelper;
import org.game.module.datingbo.DatingBO;
import org.game.module.datingbo.DatingType;
import org.game.msgBuilder.DatingMsgBuilder;
import org.game.plugin.event.GameEvent;
import org.game.plugin.event.GameEventPlugin;
import org.game.protobuf.c2s.C2SDatingMsg;
import org.game.protobuf.s2c.S2CItemMsg;
import org.game.protobuf.s2c.S2CShareMsg.RewardsMsg;
import org.game.util.ProtoUnit;

import java.util.Calendar;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;

/**
 * 约会逻辑处理者
 * Created by fxf on 2017/11/20.
 */
@SuppressWarnings({"rawtypes", "unchecked"})
public abstract class AbstractDatingHandler implements DatingHandler {

    /**
     * 约会时间段：白天
     * 非此时间段都是晚上
     */
    private static List<Integer> DAY_TIME_QUANTUM = null;

    /**
     * 开始时间：小时
     */
    private static final int BEGIN_HOUR = 0;
    /**
     * 开始时间：分钟
     */
    private static final int BEGIN_MINUTE = 1;
    /**
     * 结束时间：小时
     */
    private static final int END_HOUR = 2;
    /**
     * 结束时间：分钟
     */
    private static final int END_MINUTE = 3;

    /**
     * 对话
     */
    public byte[] dialog(Player player, C2SDatingMsg.DialogueMsg msg) {
        int branchNodeId = msg.getBranchNodeId();
        int selectedNodeId = msg.getSelectedNodeId();
        int datingType = msg.getDatingType();
        int roleId = msg.getRoleId();
        boolean isLastNode = msg.getIsLastNode();

        CurrentScriptRecord record = CurrentScriptRecordCache.me().getCurrentScriptRecordCacheProxy().getByPlayerIdDatingTypeRoleId(player.getId(), datingType, roleId);
        ToolError.isAndTrue(GameErrorCode.CLIENT_PARAM_IS_ERR,"没有找到剧本",record == null);

        checkSelectedNode(record, branchNodeId, selectedNodeId, isLastNode);

        int previousCid = record.getCurrentCid();

        // 获取当前节点和前一个节点以及分支节点
        DatingCfg previousCfg = DatingCfgCache.me().getById(previousCid);
        DatingCfg selectedCfg = DatingCfgCache.me().getById(selectedNodeId);
        DatingCfg branchCfg = DatingCfgCache.me().getById(branchNodeId);

        // 校验节点
        ToolError.isAndTrue(GameErrorCode.CLIENT_PARAM_IS_ERR, "",previousCfg.getScriptId() != selectedCfg.getScriptId());

        DatingCfg datingCfg = DatingCfgCache.me().getById(selectedNodeId);
        ItemManager.costGoodsByCid(player,datingCfg.getNodeCost(),LogDsp.newBuilder(GoodsDsp.DATING_NODE_SELECT).addAttr("datingId", datingCfg.getId()), S2CItemMsg.ItemList.newBuilder());
        ItemManager.addGoodsAndPush(player,null,null, datingCfg.getNodeReward(),LogDsp.newBuilder(GoodsDsp.DATING_NODE_SELECT).addAttr("datingId", datingCfg.getId()));

        // 处理中间节点
        dealAmongNode(player, record, previousCfg, branchCfg, selectedCfg);

        if (isFail(player,roleId,isLastNode)){
            record.delete();
            player.getPlayerProxy().sendMsg(DatingBO.datingFail,DatingMsgBuilder.createDatingFailMsg(datingCfg.getScriptId()));
            return new byte[0];
        }else{
            if (isLastNode) {
                settlement(player, selectedNodeId, datingType, roleId);
                return new byte[0];
            } else {
                return ProtoUnit.toByte(DatingMsgBuilder.getDialogueMsg(record.getScore()));
            }
        }
    }

    /**
     * 处理中间节点
     *
     * @param record          当前约会记录
     * @param previousCfg     前一个节点
     * @param branchNodeCfg   分支节点
     * @param selectedNodeCfg 选择节点
     */
    private void dealAmongNode(Player player, CurrentScriptRecord record, DatingCfg previousCfg, DatingCfg branchNodeCfg,
                               DatingCfg selectedNodeCfg) {
        DatingCfg previous = previousCfg;
        List<DatingCfg> nextNodes = getNextNodes(previous);
        List<Integer> idList = Lists.newArrayList();


        int loop = 0;
        while (nextNodes.size() > 0) {
            ToolError.isAndTrue(GameErrorCode.UNLIMITED_LOOP, "报警了死循环了", loop >= 1000);
            recordCurrentDating(player, nextNodes.get(0), record);
            previous = nextNodes.get(0);
            ToolError.isAndTrue(GameErrorCode.UNLIMITED_LOOP, "配置异常造成死循环",idList.contains(previous.getId()));
            idList.add(previous.getId());
            if (branchNodeCfg != null && previous.getId() == branchNodeCfg.getId()) {
                boolean haveSelectId = false;
                for (Integer nextId : previous.getJump()) {
                    if (nextId == selectedNodeCfg.getId()) {
                        haveSelectId = true;
                    }
                }
                ToolError.isAndTrue(GameErrorCode.BRANCH_NODE_ERR, "分支节点中没有被选中的下级节点", !haveSelectId);
                record.setCurrentCid(selectedNodeCfg.getId());
                record.getSelectedNode().add(selectedNodeCfg.getId());
                record.update();
                recordCurrentDating(player, selectedNodeCfg, record);
                break;
            }
            nextNodes = getNextNodes(previous);
            loop++;
        }
    }

    /**
     * 记录约会数据
     *
     * @param player 玩家对象
     * @param cfg    约会配置
     */
    private void recordCurrentDating(Player player, DatingCfg cfg, CurrentScriptRecord record) {

        Map reward = getReward(player, cfg, record);

        // 记录可完成次数
        if (cfg.getCompletionTimes() > 0) {
            int completionTime = (int) record.getDialogueCount().computeIfAbsent(cfg.getId(), k -> 0);
            record.getDialogueCount().put(cfg.getId(), completionTime + 1);
        }

        // 记录奖励
        if (reward != null && reward.size() > 0) {
            ItemPackageHelper.unpack(reward, null, record.getReward());
        }

        //事件
        Map inParam = Maps.newHashMap();
        inParam.put(EventConditionKey.CONDITION_TYPE, EventConditionType.DATING_CLICK);
        inParam.put(EventConditionKey.SOURCE_ID, cfg.getId());
        GameEvent event = new GameEvent(player, EventType.ROLE_CHANGE, inParam);
        GameEventPlugin.syncSubmit(event);

        // 记录积分
        record.setScore(record.getScore() + cfg.getScore());

        // 记录好感度
        record.update();
    }

    /**
     * 获取剧本对话奖励
     */
    private Map getReward(Player player, DatingCfg cfg, CurrentScriptRecord record) {
        Dating dating = DatingManager.getDating(player);
        boolean repeat = dating.getScriptIdList().contains(record.getScriptId());
        return repeat ? cfg.getRepeatReward() : cfg.getReward();
    }

    /**
     * 获取下一个节点
     */
    private List<DatingCfg> getNextNodes(DatingCfg preNode) {
        List<DatingCfg> result = Lists.newArrayList();
        int[] nextNodeIds = preNode.getJump();
        if (nextNodeIds != null && nextNodeIds.length > 0) {
            for (int nextNodeId : nextNodeIds) {
                result.add(DatingCfgCache.me().getById(nextNodeId));
            }
        }
        return result;
    }

    /**
     * 获取初始节点
     */
    int getStartNodeId(int scriptId) {
        DatingRuleCfg cfg = DatingRuleCfgCache.me().getById(scriptId);
        return cfg.getStartNodeId();
    }

    /**
     * 获取分支节点
     */
    Map<Integer, List<Integer>> getScriptBranchNode(Player player, int scriptId,C2SDatingMsg.GetScriptMsg msg) {
        List<DatingCfg> cfgList = DatingCfgCache.me().getByScriptId(scriptId);
        Map<Integer, List<Integer>> result = Maps.newHashMap();
        for (DatingCfg datingCfg : cfgList) {
            int[] jumpArr = datingCfg.getJump();
            if (jumpArr != null && jumpArr.length > 1) {
                // 符合条件的待选节点
                List<DatingCfg> waitingSelectNodes = Lists.newArrayList();
                for (int jumpCid : jumpArr) {
                    DatingCfg subNode = DatingCfgCache.me().getById(jumpCid);
                    if (checkItem(player, subNode,msg)) {
                        waitingSelectNodes.add(subNode);
                    }
                }
                // 排序并获取节点id
                List<Integer> idList = waitingSelectNodes.stream().sorted(Comparator.comparing(c -> (c.getWeighting())))// 排序
                        .map(DatingCfg::getId)// 转换成integer
                        .limit(DatingConstant.MAX_DIALOGUE_NUM)// 获取前三个
                        .collect(Collectors.toList());// 转为List
                result.put(datingCfg.getId(), idList);
            }
        }
        return result;
    }

    /**
     * 校验选择的节点id
     *
     * @param record         当前剧本记录
     * @param selectedNodeId 选择的节点id
     */
    private void checkSelectedNode(CurrentScriptRecord record, int branchNodeId, int selectedNodeId, boolean isLastNode) {
        if (isLastNode)
            return;
        Map<Integer, List<Integer>> map = record.getCurrentScript();
        List<Integer> ids = map.get(branchNodeId);
        ToolError.isAndTrue(GameErrorCode.DATING_TYPE_WRONG, ids == null);
        ToolError.isAndTrue(GameErrorCode.NODE_NOT_IN_SCRIPT, !ids.contains(selectedNodeId));
    }

    /**
     * 检查节点
     * 检查节点是否达成条件，未达成条件则不给玩家
     *
     * @param player    玩家
     * @param datingCfg 配置
     */
    private boolean checkItem(Player player, DatingCfg datingCfg,C2SDatingMsg.GetScriptMsg msg) {
        AtomicBoolean result = new AtomicBoolean(true);
        datingCfg.getCondition().forEach((k,v)->{
            String condition = (String) k;
            switch (condition){
                case DatingConstant.DATING_CONDITION_BUILD_LEVEL:
                    int buildCid = msg.getBuildId();
                    Building building = BuildingCache.me().getByPlayerIdCid(player.getId(),buildCid);
                    if (building == null){
                        result.set(false);
                        return;
                    }
                    break;
                case DatingConstant.DATING_CONDITION_MOOD:
                    if (player.getPlayerProxy().getCurrentRole().getMood() < (int)v){
                        result.set(false);
                        return;
                    }
                    break;
                case DatingConstant.DATING_CONDITION_TIME:
                    if ((int)v != getTimeQuantum()){
                        result.set(false);
                        return;
                    }
                    break;
                case DatingConstant.DATING_CONDITION_OWN_ROLE:
                    List idList = (List) v;
                    for (Object id : idList) {
                        Role role = RoleCache.me().getByCidPlayerId((Integer) id,player.getId());
                        if (role == null){
                            result.set(false);
                            return;
                        }
                    }
                    break;
            }
        });
        return result.get();
    }


    /**
     * 结算
     */
    private void settlement(Player player, int selectedNodeCid, int datingType, int roleCid) {

        CurrentScriptRecord record = CurrentScriptRecordCache.me().getCurrentScriptRecordCacheProxy().getByPlayerIdDatingTypeRoleId(player.getId(), datingType, roleCid);

        Map rewardMap = record.getReward();
        Role role = RoleCache.me().getByCidPlayerId(roleCid, player.getId());

        //计算好感度
        int favor = calcFavor(record, rewardMap, role);
        //计算心情值
        calcMood(rewardMap, role);


        Dating dating = DatingManager.getDating(player);
        dating.getDatingProxy().addDialogueCount(record.getDialogueCount());

        DatingCfg cfg = DatingCfgCache.me().getById(selectedNodeCid);


        if (!dating.getScriptIdList().contains(cfg.getScriptId())){
            dating.getScriptIdList().add(cfg.getScriptId());
        }
        Map<String, Object> in = Maps.newHashMap();
        // 结局
        if (!dating.getEndNodeList().contains(cfg.getId())){
            dating.getEndNodeList().add(cfg.getId());
            in.put(EventConditionKey.END_TYPE, cfg.getEndType());
            ElementCollectManager.recordElement(player, ElementCollectionConstant.KEY_ENDING,cfg.getId());
        }

        dating.update();
        
        // 各子类结束需要记录的数据都写到这里面
        endDatingRecord(player, dating, cfg, roleCid,record);
        
        LogDsp log = LogDsp.newBuilder(GoodsDsp.DATING_REWARD).targetTag(record.getScriptId());
        List<RewardsMsg> rewardsMsg = ItemManager.me().unpackNotify(player, null ,role, rewardMap, log);

        player.getPlayerProxy().sendMsg(MessageManager.me().create(DatingBO.sendSettlementInfo,ProtoUnit.toByte(DatingMsgBuilder.getDatingSettlementMsg(record.getScore(), favor, cfg.getScriptId(), rewardsMsg))));

		in.put(EventConditionKey.CONDITION_TYPE, EventConditionType.FINISH_DATING_COUNT);
		in.put(EventConditionKey.ROLE_CID, player.getPlayerProxy().getCurrentRole().getCid());
		in.put(EventConditionKey.DATING_TYPE, datingType);
		in.put(EventConditionKey.SCRIPT_ID, record.getScriptId());

		GameEvent event = new GameEvent(player, EventType.ROLE_CHANGE, in);
		GameEventPlugin.syncSubmit(event);

        LogDating.me().log(player.getId(),datingType,record.getScriptId(),player.getPlayerProxy().getCurrentRole().getCid(), LogDsp.newBuilder(DatingDsp.FINISH_DATING));
    }

    /**
     * 计算心情值
     * */
    private void calcMood(Map rewardMap, Role role) {
        int mood = (int) rewardMap.computeIfAbsent(ItemConstantId.ROLE_MOOD, k -> 0);
        if (mood != 0){
            mood *= (100 + role.getRoleProxy().getEffectByType(RoleConstant.EFFECT_TYPE_4))/100;
        }
        rewardMap.put(ItemConstantId.ROLE_MOOD, mood);
    }

    /**
     * 计算好感度
     * @param record 当前约会记录
     * @param rewardMap 约会奖励
     * @param role 精灵
     */
    private int calcFavor(CurrentScriptRecord record, Map rewardMap, Role role) {
        //1.判断本次约会是否奖励好感度
        int favor = (int) rewardMap.computeIfAbsent(ItemConstantId.ROLE_FAVOR, k -> 0);
        if (favor == 0)
            return 0;

        //2.计算约会得分对好感度的加成
        int finalFavor = calcFavor(favor, record.getScore()) ;
        //3. 计算房间对好感度加成
        finalFavor *= (100 + role.getRoleProxy().getEffectByType(RoleConstant.EFFECT_TYPE_5))/100;
        rewardMap.put(ItemConstantId.ROLE_FAVOR, finalFavor);
        return finalFavor;
    }

    /**
     * 获取当前剧本记录
     *
     * @param player 玩家对象
     */
    protected CurrentScriptRecord getOrInitCurrentScriptRecord(Player player,List<Integer> roleId) {
        int datingType = getDatingType();
        CurrentScriptRecord record = CurrentScriptRecordCache.me().getCurrentScriptRecordCacheProxy().getByPlayerIdDatingTypeRoleId(player.getId(), datingType, roleId);
        if (record == null) {
            record = new CurrentScriptRecord(0, Maps.newHashMap(), Maps.newHashMap(),datingType,60,0,0, Maps.newHashMap(),0, Lists.newArrayList(),player.getId(),Lists.newArrayList());
            record.getRoleIds().addAll(roleId);
            record.insert();
        }
        return record;
    }

    /**
     * 创建剧本记录
     */
    void createCurrentScriptRecord(Player player, int scriptId, Map<Integer, List<Integer>> branchNodes, int startNodeId, List<Integer> roleIds) {
        CurrentScriptRecord record  = new CurrentScriptRecord(0, Maps.newHashMap(), Maps.newHashMap(),getDatingType(),60,0,0, Maps.newHashMap(),0, Lists.newArrayList(),player.getId(),Lists.newArrayList());
        record.getRoleIds().addAll(roleIds);
        record.setCurrentScript(branchNodes)
                .setCurrentCid(startNodeId)
                .setScriptId(scriptId);
        record.insert();
    }

    /**
     * 约会结束数据记录
     */
    protected void endDatingRecord(Player player, Dating dating, DatingCfg cfg, int roleCid,CurrentScriptRecord record){
        record.delete();
    }

    /**
     * 计算约会得分对好感度的加成
     * @param favor 好感度
     * @param score 约会得分
     * @return
     */
    private int calcFavor(int favor, int score) {
        Map<Integer, Integer> ruleMap = (Map<Integer, Integer>) DiscreteDataCfgCache.me().getData(DiscreteDataID.DATING, DiscreteDataKey.DATING_REWARD_FAVOR_RULE);
        int percent = ruleMap.keySet().stream()
                .filter(k -> score >= k || (score < 0 && k == 0))
                .max(Integer::compareTo)
                .map(k -> ruleMap.get(k))
                .get();
        return favor * percent / 100;
    }

    /**
     * 获取约会时间段
     *
     * @return 0：白天 1：晚上
     */
    protected static int getTimeQuantum() {
        if (DAY_TIME_QUANTUM == null) {
            DAY_TIME_QUANTUM = (List<Integer>) DiscreteDataCfgCache.me().getData(DiscreteDataID.DATING, DiscreteDataKey.DATING_DAY);
        }
        Calendar now = Calendar.getInstance();
        Calendar time = DateUtil.beginOfDay(Calendar.getInstance());
        time.set(Calendar.HOUR_OF_DAY, DAY_TIME_QUANTUM.get(BEGIN_HOUR));
        time.set(Calendar.MINUTE, DAY_TIME_QUANTUM.get(BEGIN_MINUTE));
        if (now.getTimeInMillis() < time.getTimeInMillis())
            return DatingConstant.DATING_FRAME_NIGHT;
        time.set(Calendar.HOUR_OF_DAY, DAY_TIME_QUANTUM.get(END_HOUR));
        time.set(Calendar.MINUTE, DAY_TIME_QUANTUM.get(END_HOUR));
        if (now.getTimeInMillis() > time.getTimeInMillis())
            return DatingConstant.DATING_FRAME_NIGHT;
        return DatingConstant.DATING_FRAME_DAY;
    }

    protected int getDatingType(){
        DatingType annotation =  this.getClass().getAnnotation(DatingType.class);
        return annotation.value();
    }

    void updateCity(Player player, DatingRuleCfg cfg){
        int cityCid = cfg.getEnterCondtionByType(DatingConstant.ENTER_CDTION_KEY_CITY_CID);
        City city = CityCache.me().getByCidPlayerId(cityCid, player.getId());
        city.setInDating(false);
        city.update();

    }

    @Override
    public boolean isFail(Player player, int roleCid, boolean isLastNode) {
        return false;
    }

}
